package com.ocm.controller;

import java.util.ArrayList;
import java.util.List;

import net.sf.json.JSONArray;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

import com.ocm.controller.mapper.JsonMapping;
import com.ocm.controller.model.SecuritySearchForm;
import com.ocm.controller.util.OCMUtil;
import com.ocm.controller.validator.ClassDefValidator;
import com.ocm.core.constant.OCMConstant;
import com.ocm.core.domain.AccessMask;
import com.ocm.core.domain.ClassDefinitionType;
import com.ocm.core.domain.Permission;
import com.ocm.core.dto.ClassDefinitionDto;
import com.ocm.core.dto.ClassInstanceDto;
import com.ocm.core.dto.GroupDto;
import com.ocm.core.dto.PropertyDefinitionDto;
import com.ocm.core.dto.SecurityDto;
import com.ocm.core.dto.StorageDto;
import com.ocm.core.dto.UserInfoDto;
import com.ocm.core.mapping.DTOConfiguration;
import com.ocm.core.mapping.IDMapping;
import com.ocm.core.service.ClassDefService;
import com.ocm.core.service.ClassInstService;
import com.ocm.core.service.PropertyDefService;
import com.ocm.core.service.SecurityService;

@Controller
@SessionAttributes("finalCassDef")
public class ClassDefController {

   public static final String ACTIONS_PAGE = "classDef/actions";
   public static final String GENERAL_PAGE = "classDef/general";
   public static final String ADD_PAGE = "classDef/add";
   public static final String PROPERTY_PAGE = "classDef/property";
   public static final String SECURITY_PAGE = "classDef/security";
   public static final String FINAL_PAGE = "classDef/finalCreate";
   public static final String STATUS_PAGE = "classDef/status";
   public static final String EDIT_GENERAL_PAGE = "classDef/editGeneral";
   public static final String EDIT_PAGE = "classDef/edit";
   public static final String FINAL_EDIT_PAGE = "classDef/finalEdit";
   public static final String SECURITIES_PAGE = "security/list";
   public static final String EDIT_SECURITY_PAGE = "security/edit";

   private ClassDefService classDefService;
   private ClassInstService classInstService;
   private PropertyDefService propertyDefService;
   private SecurityService securityService;
   private JsonMapping jsonMapping;
   private ClassDefValidator classDefValidator;

   @Autowired
   public ClassDefController(
      @Qualifier("classDefService") ClassDefService classDefService,
      @Qualifier("classInstService") ClassInstService classInstService,
      @Qualifier("propertyDefService") PropertyDefService propertyDefService,
      @Qualifier("securityService") SecurityService securityService,
      @Qualifier("jsonMapping") JsonMapping jsonMapping,
      @Qualifier("classDefValidator") ClassDefValidator classDefValidator) {
      this.classDefService = classDefService;
      this.classInstService = classInstService;
      this.propertyDefService = propertyDefService;
      this.jsonMapping = jsonMapping;
      this.classDefValidator = classDefValidator;
      this.securityService = securityService;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/viewAction", method = RequestMethod.GET)
   public String viewActionPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model, SessionStatus status) {
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);
      if (model.containsKey("finalCassDef")){
         model.remove("finalCassDef");
      }

      ClassDefinitionDto classDefinitionDto = this.classDefService.findId(classDefId, DTOConfiguration.instance(IDMapping.CLASSDEF_LIMIT));
      model.addAttribute("classDef", classDefinitionDto);
      List<ClassInstanceDto> classInsts = this.classInstService.findClassInstChilds(classDefId, 
            DTOConfiguration.instance(IDMapping.INST_PROPERTY_EXCLUDED_DATA));
      model.addAttribute("classInsts", classInsts);
      return ACTIONS_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/viewGeneralPage", method = RequestMethod.GET)
   public String viewMenuPage(@PathVariable("selectedClassDefId")long classDefId,
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);
      return GENERAL_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/description/add", method = RequestMethod.GET)
   public String viewAddPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);
      return ADD_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/description/add", method = RequestMethod.POST)
   public String addClassDef(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, 
         @ModelAttribute("classDef") ClassDefinitionDto classDefinitionDto, BindingResult result, ModelMap model) {
      ClassDefinitionDto parentClass = this.classDefService.findId(classDefId, DTOConfiguration.instance(IDMapping.CLASSDEF_LIMIT));
      StorageDto storage = parentClass.getStorage();
      this.classDefValidator.validateClassDef(storage.getId(), classDefinitionDto, result);
      if (!result.hasErrors()){
         classDefinitionDto.setStorage(storage);
         classDefinitionDto.setSecurities(getDefaultClassSecurities());
         model.addAttribute("finalCassDef", classDefinitionDto);
      }
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/property/assign", method = RequestMethod.GET)
   public String viewClassProperties(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      ClassDefinitionDto classDefinitionDto =  (ClassDefinitionDto) model.get("finalCassDef");
      StorageDto storage = classDefinitionDto.getStorage();
      List<PropertyDefinitionDto> properties = (classDefinitionDto == null) ? new ArrayList<PropertyDefinitionDto>() : classDefinitionDto.getPropertyDefs(); 
      List<PropertyDefinitionDto> removeProperies = (classDefinitionDto == null) ? new ArrayList<PropertyDefinitionDto>() : this.classDefService.getAllProperties(classDefId);
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);
      model.addAttribute("restProperties", OCMUtil.getRestProperties(propertyDefService.findAllFromStorageId(storage.getId()), removeProperies));
      model.addAttribute("assignedProperties", removeProperies);
      model.addAttribute("systemProperties", new ArrayList<PropertyDefinitionDto>());
      if (classDefinitionDto.getId() != null && classDefinitionDto.getId() > 0 ){
         if (classDefType == ClassDefinitionType.CUSTOMOBJECTDEF.getClassDefType()){
            model.addAttribute("properties", OCMUtil.getEditabledProperties(true, properties, OCMConstant.DOCUMENT_TITLE));
            if (classDefinitionDto.getParentClassDefUID() == null){
               model.addAttribute("systemProperties", OCMUtil.getEditabledProperties(false, properties));
            }
         }else if (classDefType == ClassDefinitionType.DOCUMENTCLASSDEF.getClassDefType()){
            model.addAttribute("properties", OCMUtil.getEditabledProperties(true, properties));
            if (classDefinitionDto.getParentClassDefUID() == null){
               model.addAttribute("systemProperties", OCMUtil.getEditabledProperties(false, properties));
            }
         }
      }else {
         model.addAttribute("properties", properties);
      }
      return PROPERTY_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/security/list", method = RequestMethod.GET)
   public String viewSecurities(@PathVariable("selectedClassDefId")long classDefId,
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      ClassDefinitionDto classDefinitionDto =  (ClassDefinitionDto) model.get("finalCassDef");
      List<SecurityDto> existSecurityDtos = classDefinitionDto.getSecurities();
      model.put("securities", existSecurityDtos);
      model.put("selectedClassDefId", classDefId);
      model.put("classDefType", classDefType);
      return SECURITIES_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/security/assign", method = RequestMethod.GET)
   public String viewSecurityPage(@PathVariable("selectedClassDefId")long classDefId,
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.put("selectedClassDefId", classDefId);
      model.put("classDefType", classDefType);
      return SECURITY_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/security/assign", method = RequestMethod.POST)
   public String assignSecurityPage(@RequestParam("JSONSecurityIds") String jsonSecurities,
         @PathVariable("selectedClassDefId")long classDefId,
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      Long[] ids = OCMUtil.convertIDsFromJsonString(jsonSecurities);
      List<UserInfoDto> userInfoDtos = this.securityService.findAll(ids);
      List<SecurityDto> securityDtos = OCMUtil.getDefaultSecurities(userInfoDtos);
      ClassDefinitionDto classDefinitionDto = (ClassDefinitionDto) model.get("finalCassDef");
      List<SecurityDto> existSecurityDtos = classDefinitionDto.getSecurities();
      if (existSecurityDtos == null){
         existSecurityDtos = new ArrayList<SecurityDto>();
         classDefinitionDto.setSecurities(existSecurityDtos);
      }
      for (SecurityDto securityDto : securityDtos){
         if (!existSecurityDtos.contains(securityDto)){
            existSecurityDtos.add(securityDto);
         }
      }
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/security/search", method = RequestMethod.POST)
   public @ResponseBody JSONArray searchSecurity(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType,
         @ModelAttribute("secSearchform")SecuritySearchForm searchForm, ModelMap model) {
      JSONArray jsonArray = null;
      if (SecuritySearchForm.TYPE.USER.getType().equals(searchForm.getType())){
         List<UserInfoDto> userDtos = this.securityService.searchByUserName(searchForm.getSearchName());
         jsonArray = this.jsonMapping.convertSearchUsers(userDtos);
      }else if (SecuritySearchForm.TYPE.GROUP.getType().equals(searchForm.getType())){
         List<GroupDto> groupDtos = this.securityService.searchByGroupName(searchForm.getSearchName());
         jsonArray = this.jsonMapping.convertSearchGroups(groupDtos);
      }else if (SecuritySearchForm.TYPE.BOTH.getType().equals(searchForm.getType())){
         List<UserInfoDto> userDtos = this.securityService.searchByName(searchForm.getSearchName());
         jsonArray = this.jsonMapping.convertSearchUsers(userDtos);
      }
      return jsonArray;
   }

   @RequestMapping(value = "/classDef/security/{userInfoId}/edit", method = RequestMethod.GET)
   public String viewEditSecurityPage(@PathVariable("userInfoId")long userInfoId, ModelMap model) throws Exception {
      ClassDefinitionDto classDefinitionDto =  (ClassDefinitionDto) model.get("finalCassDef");
      List<SecurityDto> existSecurityDtos = classDefinitionDto.getSecurities();
      for (SecurityDto securityDto : existSecurityDtos){
         if (securityDto.getUserId() == userInfoId){
            model.put("security",securityDto);
            Permission[] permissions = Permission.getPermissions(securityDto.getAccessMask());
            model.put("leftData",jsonMapping.convertPermisson(AccessMask.FULLPERMISSION.getRestPermissions(permissions)).toString());
            model.put("rightData",jsonMapping.convertPermisson(permissions).toString());
            break;
         }
      }
      return EDIT_SECURITY_PAGE;
   }

   @RequestMapping(value = "/classDef/security/{userInfoId}/remove", method = RequestMethod.POST)
   public String removeSecurityPage(@PathVariable("userInfoId")long userInfoId, ModelMap model) throws Exception {
      ClassDefinitionDto classDefinitionDto = (ClassDefinitionDto) model.get("finalCassDef");
      List<SecurityDto> existSecurityDtos = classDefinitionDto.getSecurities();
      for (SecurityDto securityDto : existSecurityDtos){
         if (securityDto.getUserId() == userInfoId){
            existSecurityDtos.remove(securityDto);
            break;
         }
      }
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classDef/security/{userInfoId}/edit", method = RequestMethod.POST)
   public String editSecurityPage(@RequestParam("JSONPermissions") String jsonPermission,
         @PathVariable("userInfoId")long userInfoId, ModelMap model) throws Exception {
      ClassDefinitionDto classDefinitionDto = (ClassDefinitionDto) model.get("finalCassDef");
      List<SecurityDto> existSecurityDtos = classDefinitionDto.getSecurities();
      for (SecurityDto securityDto : existSecurityDtos){
         if (securityDto.getUserId() == userInfoId){
            int[] permissions = OCMUtil.convertFromJsonString(jsonPermission);
            int accessMask=0; 
            for (int permission:permissions){
               accessMask += permission;
            }
            securityDto.setAccessMask(accessMask);
            break;
         }
      }
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/property/assign", method = RequestMethod.POST)
   public String assignPropertyDef(@RequestParam("JSONPropertyIds") String jsonProperties, 
         @PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      Long[] propertyDefIds = OCMUtil.convertIDsFromJsonString(jsonProperties);
      List<PropertyDefinitionDto> propertyDtos = this.propertyDefService.findByIds(propertyDefIds);
      ClassDefinitionDto classDefinitionDto =  (ClassDefinitionDto) model.get("finalCassDef");
      classDefinitionDto.setPropertyDefs(propertyDtos);
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/create", method = RequestMethod.GET)
   public String viewCreate(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);
      model.addAttribute("classDef", (ClassDefinitionDto) model.get("finalCassDef"));
      return FINAL_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/create", method = RequestMethod.POST)
   public String createClassDef(@PathVariable("selectedClassDefId")long parentClassDefId, 
         @PathVariable("classDefType")int classDefType, 
         @ModelAttribute("classDef") ClassDefinitionDto classDefinitionDto, 
         BindingResult result, ModelMap model, SessionStatus status) {
      classDefinitionDto =  (ClassDefinitionDto) model.get("finalCassDef");
      this.classDefValidator.validate(classDefinitionDto, result);
      if (!result.hasErrors()){
         classDefinitionDto.setClassDefinitionType(ClassDefinitionType.DOCUMENTCLASSDEF.getClassDefType());
         this.classDefService.create(parentClassDefId, classDefinitionDto);
         status.setComplete();
      }
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/viewEditGeneralPage", method = RequestMethod.GET)
   public String viewEditGeneralPage(
         @PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);
      if (model.containsKey("finalCassDef")){
         model.remove("finalCassDef");
      }
      ClassDefinitionDto classDefinitionDto = this.classDefService.findId(classDefId, DTOConfiguration.instance(IDMapping.CLASSDEF_DEEP));
      model.addAttribute("finalCassDef", classDefinitionDto);
      return EDIT_GENERAL_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/description/edit", method = RequestMethod.GET)
   public String viewEditPage(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);
      ClassDefinitionDto classDefinitionDto = (ClassDefinitionDto) model.get("finalCassDef");
      model.addAttribute("classDef", classDefinitionDto);
      return EDIT_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/description/edit", method = RequestMethod.POST)
   public String editClassDef(@ModelAttribute("classDef") ClassDefinitionDto classDefinitionDto, 
         @PathVariable("selectedClassDefId")long classDefId, @PathVariable("classDefType")int classDefType,
         BindingResult result, ModelMap model) {
      this.classDefValidator.validate(classDefinitionDto, result);
      if (!result.hasErrors()){
         ClassDefinitionDto oldClassDefinitionDto = (ClassDefinitionDto) model.get("finalCassDef");
         oldClassDefinitionDto.setDisplayName(classDefinitionDto.getDisplayName());
         oldClassDefinitionDto.setDescription(classDefinitionDto.getDescription());
      }
      return STATUS_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/finalEdit", method = RequestMethod.GET)
   public String viewFinalEdit(@PathVariable("selectedClassDefId")long classDefId, 
         @PathVariable("classDefType")int classDefType, ModelMap model) {
      model.addAttribute("classDefType", classDefType);
      model.addAttribute("selectedClassDefId", classDefId);
      model.addAttribute("classDef", (ClassDefinitionDto) model.get("finalCassDef"));
      return FINAL_EDIT_PAGE;
   }

   @RequestMapping(value = "/classDef/{classDefType}/{selectedClassDefId}/update", method = RequestMethod.POST)
   public String updateClassDef(@PathVariable("selectedClassDefId")long parentClassDefId, 
         @PathVariable("classDefType")int classDefType, 
         @ModelAttribute("classDef") ClassDefinitionDto classDefinitionDto, 
         BindingResult result, ModelMap model, SessionStatus status) {
      classDefinitionDto =  (ClassDefinitionDto) model.get("finalCassDef");
      this.classDefValidator.validate(classDefinitionDto, result);
      if (!result.hasErrors()){
         this.classDefService.update(classDefinitionDto);
         status.setComplete();
      }
      return STATUS_PAGE;
   }

   public List<SecurityDto> getDefaultClassSecurities(){
      List<SecurityDto> securityDtos = new ArrayList<SecurityDto>();
      UserInfoDto userInfoDto = this.securityService.findUserInfoByGroupname(OCMConstant.SEC_ANONYMOUS_GROUP);
      SecurityDto securityDto = new SecurityDto();
      securityDto.setAccessMask(AccessMask.VIEWPROPERTIES.getAccessMask());
      securityDto.setHasGroup(true);
      securityDto.setHasAnonymous(true);
      securityDto.setHasUser(false);
      securityDto.setHasCreator(false);
      securityDto.setUserId(userInfoDto.getId());
      securityDto.setUserInfo(userInfoDto);
      securityDtos.add(securityDto);

      userInfoDto = this.securityService.getCurrentUsername();
      securityDto = new SecurityDto();
      securityDto.setAccessMask(AccessMask.FULLPERMISSION.getAccessMask());
      securityDto.setHasGroup(false);
      securityDto.setHasUser(false);
      securityDto.setHasAnonymous(false);
      securityDto.setHasCreator(true);
      securityDto.setUserId(userInfoDto.getId());
      securityDto.setUserInfo(userInfoDto);
      securityDtos.add(securityDto);
      return securityDtos;
  }
}